/*
 * Copyright 2007 - The JDPF Project Team (http://www.jdpf.org) 
 * 
 * Licensed under the Apache License, Version 2.0 (the "License"); 
 * you may not use this file except in compliance with the License. 
 * You may obtain a copy of the License at 
 * 
 * http://www.apache.org/licenses/LICENSE-2.0 
 * 
 * Unless required by applicable law or agreed to in writing, software 
 * distributed under the License is distributed on an "AS IS" BASIS, 
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and 
 * limitations under the License. 
 */
package org.jdpf.core.netloader;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.jdpf.core.kernel.elements.AbstractArc;
import org.jdpf.core.kernel.elements.AbstractNetElement;
import org.jdpf.core.kernel.elements.ArcExpression;
import org.jdpf.core.kernel.elements.ITransition;
import org.jdpf.core.kernel.elements.InputArc;
import org.jdpf.core.kernel.elements.Net;
import org.jdpf.core.kernel.elements.OutputArc;
import org.jdpf.core.kernel.elements.Place;
import org.jdpf.core.kernel.logging.ILogger;
import org.jdpf.core.kernel.logging.LoggerFactory;
import org.jdpf.core.kernel.plugins.BundleToolBox;
import org.jdpf.core.kernel.plugins.IPluginsFrameworkConstants;
import org.jdpf.core.plugins.blocks.AbstractTransitionPlugin;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;
import org.xml.sax.helpers.DefaultHandler;

/**
 * This class is the Event Handler for the Sax Parser.
 * <p>
 * It is responsible for parsing an XML Net file and can return a Net object. An
 * event method <code>endParsing(Net)</code> is invoked when the parsing is
 * finished to a listener class (the Event Handler for this Net Parser).
 * </p>
 * 
 * {@link org.jdpf.core.netloader.NetParserListener See NetParserListener}
 */
public class NetEventHandler extends DefaultHandler {
	
	private static final ILogger logger=LoggerFactory.getDefault().getLogger();

	private Net net;
	private AbstractNetElement e;
	private Map<String, Place> places;
	private Map<String, AbstractArc> arcs;
	private Map<String, ITransition> transitions;
	private AbstractTransitionPlugin plugin;
	private NetParserListener listener;
	private String fileName;
	private String state, parentState, transitionId, actualPluginId;
	private List<String> pluginsId;

	/**
	 * The constructor of a NetEventHandler object. You have to pass a
	 * NetParserListener that is used as a listener of events generated by this
	 * parser.
	 * 
	 * @param listener
	 *            The listener of events generated from this Net Parser
	 */
	public NetEventHandler(NetParserListener listener, String fileName) {
		this.fileName = fileName;
		this.listener = listener;
		pluginsId = new ArrayList<String>(5);
		places = new HashMap<String, Place>();
		transitions = new HashMap<String, ITransition>();
		arcs = new HashMap<String, AbstractArc>();
	}

	@Override
	public void characters(char[] ch, int start, int length)
			throws SAXException {
		if (state.equals("Name")) {
			String name = new String(ch, start, length);
			if (parentState.equals("Place")) {
				e.setName(name);
			} else if (parentState.equals("Transition")) {
				plugin = BundleToolBox.getDefault().getPlugin(actualPluginId, transitionId,
						name);
			} else if (parentState.equals("Arc")) {
				e.setName(name);
			} else if (parentState.equals("JDPFnet")) {
				net.setName(name);
			} else if (parentState.equals("Inspector")) {
				plugin = BundleToolBox.getDefault().getPlugin(actualPluginId, transitionId,
						name);
//				change category to 'inspector' because it also could be 'serializer'
				plugin.setCategory(IPluginsFrameworkConstants.INSPECTOR); 
			}
		}
	}

	@Override
	public void endElement(String uri, String localName, String qName)
			throws SAXException {
		if (localName.equals("Place")) {
			places.put(e.getId(), (Place) e);
		} else if (localName.equals("Transition")) {
			transitions.put(transitionId, plugin);
		} else if (localName.equals("Inspector")) {
			((Place) e).addInspector(plugin);
			parentState = "Place";
			net.add(plugin);
		} else if (localName.equals("Arc")) {
			arcs.put(e.getId(), (AbstractArc) e);
		}
		state = parentState;
	}

	@Override
	public void startElement(String uri, String localName, String qName,
			Attributes attributes) throws SAXException{
		if (localName.equals("JDPFnet")) {
		} else if (localName.equals("Tool")) {
			// no op.
		} else if (localName.equals("Plugin")) {
			String pluginId = attributes.getValue("id");
			pluginsId.add(pluginId);
		} else if (localName.equals("Place")) {
			e = new Place(attributes.getValue("id"));
		} else if (localName.equals("Transition")||localName.equals("Inspector")) {
			actualPluginId = attributes.getValue("pluginId");
			if (pluginsId.contains(actualPluginId)) {
				transitionId = attributes.getValue("id");
			} else {
				logger.error(this,"Unable to find the plugin "+actualPluginId
						+" between declared plugins.");
				net.setValid(false);
			}
		} else if (localName.equals("Arc")) {
			String id = attributes.getValue("id");
			String source = attributes.getValue("source");
			String destination = attributes.getValue("destination");
			String orderString = attributes.getValue("inputOrder");
			int order = 0;
			if (orderString != null) {
				order = Integer.parseInt(orderString);
			}
			if (transitions.containsKey(source)) {
				ITransition t = transitions.get(source);
				Place p = places.get(destination);
				e = new OutputArc(id, t, p);
			} else if (places.containsKey(source)) {
				Place p = places.get(source);
				ITransition t = transitions.get(destination);
				e = new InputArc(id, p, t);
				InputArc a = (InputArc) e;
				a.setOrder(order);
				e = a;
			} else {
				logger.error(this,"Unable to find the source element for the arc "
						+id);
			}

		}
		parentState = state;
		state = localName;
	}

	@SuppressWarnings("unchecked")
	@Override
	public void endDocument() {
		for (ITransition t : transitions.values()) {
			List orders = new ArrayList(5);
			for (InputArc arc : t.getInputArcs()) {
				if (orders.contains(arc.getOrder())) {
					net.setValid(false);
					logger.error(this,"The input arc "
							+ arc.getName() + " has a wrong order number ("
							+ arc.getOrder() + ")."
							+ "Another arc with the same order already "
							+ "exists.");
					
				} else {
					orders.add(arc.getOrder());
				}
			}
			net.add(t);
		}
		for (Place el : places.values()) {
			net.add(el);
		}
		for (AbstractArc arc : arcs.values()) {
			if (arc instanceof InputArc) {
				InputArc a = (InputArc) arc;
				a.setExpression(new ArcExpression());
				net.add(a);
			} else {
				OutputArc a = (OutputArc) arc;
				net.add(a);
			}

		}
		listener.endParsing(net);
	}

	@Override
	public void startDocument() throws SAXException {
		net = new Net();
		net.setValid(true);
		net.setName(fileName);
	}
	
	public void fatalError(SAXParseException e){
		logger.fatal("Unable to parse net file "+e.getSystemId());
		logger.fatal("Error found at line "+e.getLineNumber()+" while parsing "+e.getSystemId());
		logger.fatal("["+e.getLocalizedMessage()+"]\n");
		net.setValid(false);
	}
	
	public void error(SAXParseException e){
		if(e.getMessage().startsWith("cvc-elt")){
			logger.log(e.getMessage());
			net.setValid(false);
		}
	}

}
